<?php

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * Description of DataImporterTravide
 *
 * @author jay
 */
class DataImporterTravide extends DataImporter {

    private $minExportVersion = 1;
    private $maxExportVersion = 2;
    private $resourceBasePath = '';
    private $resourceIngestPath = '';
    private $maxConnectionInserts = 100;
    private $currentInsertsCount = 0;

    public function setResourceBasePath($resourceBasePath) {
        $this->resourceBasePath = $resourceBasePath;
    }

    public function getResourceBasePath() {
        return $this->resourceBasePath;
    }

    public function setResourceIngestPath($resouseIngestPath) {
        $this->resourceIngestPath = $resouseIngestPath;
    }

    public function getResourceIngestPath() {
        return $this->resourceIngestPath;
    }

    private function logMessage($message, $section = '') {
        print($section . "\t" . $message . "\n");
    }

    private function resetDoctrineConnection() {
        if (sfConfig::get('app_fixes_database_force_impexp_reset', true)) {
            Doctrine_Manager::connection()->close();
        }
        $this->currentInsertsCount = 0;
    }

    private function advanceAndCheckInsertCounter() {
        $this->currentInsertsCount++;
        if ($this->currentInsertsCount > $this->maxConnectionInserts) {
            $this->resetDoctrineConnection();
        }
    }

    private function importMasterDataPropertyDetailObject(DOMElement $node) {
        $propertyDetail = Doctrine::getTable('PropertyDetail')->getOneByIdentifier($this->decodeSpecialCharacters($node->nodeValue));

        /**
         * Add this check to ensure we're not about to try and add a property
         * becuase the identifier can't be found but the name already exists as
         * duplicate names will fail
         */
        if (Doctrine::getTable('PropertyDetail')->checkIsNameAlreadyInUse($this->decodeSpecialCharacters($node->getAttribute('name')), $this->decodeSpecialCharacters($node->nodeValue))) {
            /**
             * The name is already in user override it.
             * if this fails then the import will require manual intervention to fix
             */
            $node->setAttribute('name', $node->getAttribute('name') . ' (' . $node->nodeValue . ')');
            $this->logMessage('Altering Imported Property: ' . $this->decodeSpecialCharacters($node->nodeValue) . ' = ' . $this->decodeSpecialCharacters($node->getAttribute('name')));
        }
        if ($propertyDetail == null) {
            $this->logMessage('Adding Property: ' . $node->nodeValue);
            $propertyDetail = new PropertyDetail();
            $propertyDetail->setTypeValue($this->decodeSpecialCharacters($node->nodeValue));
        } else {
            $this->logMessage('Updating Property: ' . $node->nodeValue);
        }
        $propertyDetail->setTypeName($this->decodeSpecialCharacters($node->getAttribute('name')));
        $propertyDetail->setDisplayGroupBy($this->decodeSpecialCharacters($node->getAttribute('displayGroupBy')));
        $propertyDetail->save();
        /**
         * TODO: Handle Deleted/Archived Properties, add in display group handling
         */
    }

    private function importMasterDataRelationTypeObject(DOMElement $node) {
        $relationType = Doctrine::getTable('RelationType')->getOneByIdentifier($this->decodeSpecialCharacters($node->nodeValue));
        /**
         * Add this check to ensure we're not about to try and add a relation
         * becuase the identifier can't be found but the name already exists as
         * duplicate names will fail
         */
        if (Doctrine::getTable('RelationType')->checkIsNameAlreadyInUse($this->decodeSpecialCharacters($node->getAttribute('name')), $this->decodeSpecialCharacters($node->nodeValue))) {
            /**
             * The name is already in user override it.
             * if this fails then the import will require manual intervention to fix
             */
            $node->setAttribute('name', $node->getAttribute('name') . ' (' . $node->nodeValue . ')');
            $this->logMessage('Altering Imported Relation: ' . $node->nodeValue . ' = ' . $node->getAttribute('name'));
        }
        if ($relationType == null) {
            $this->logMessage('Adding Relation Type: ' . $node->nodeValue);
            $relationType = new RelationType();
            $relationType->setTypeValue($this->decodeSpecialCharacters($node->nodeValue));
        } else {
            $this->logMessage('Updating Relation Type: ' . $node->nodeValue);
        }

        $relationType->setTypeName($this->decodeSpecialCharacters($node->getAttribute('name')));
        $relationType->setNameInverse($this->decodeSpecialCharacters($node->getAttribute('nameInverse')));
        $relationType->setValueInverse($this->decodeSpecialCharacters($node->getAttribute('valueInverse')));
        $relationType->setDisplayGroupBy($this->decodeSpecialCharacters($node->getAttribute('displayGroupBy')));
        /**
         * TODO: Handle Deleted/Archived Relation Types, add in display group
         * and inverse name and inverse type handling
         */
        $relationType->save();
    }

    private function importMasterDataCollectionTypeObject(DOMElement $node) {
        $collectionType = Doctrine::getTable('CollectionType')->getOneByIdentifier($this->decodeSpecialCharacters($node->nodeValue));

        /**
         * Add this check to ensure we're not about to try and add a CollectionTYpe
         * becuase the identifier can't be found but the name already exists as
         * duplicate names will fail
         */
        if (Doctrine::getTable('CollectionType')->checkIsNameAlreadyInUse($this->decodeSpecialCharacters($node->getAttribute('name')), $this->decodeSpecialCharacters($node->nodeValue))) {
            /**
             * The name is already in user override it.
             * if this fails then the import will require manual intervention to fix
             */
            $node->setAttribute('name', $node->getAttribute('name') . ' (' . $node->nodeValue . ')');
            $this->logMessage('Altering Imported Collection Type: ' . $node->nodeValue . ' = ' . $node->getAttribute('name'));
        }

        if ($collectionType == null) {
            $this->logMessage('Adding Collection Type: ' . $node->nodeValue);
            $collectionType = new CollectionType();
            $collectionType->setTypeValue($this->decodeSpecialCharacters($node->nodeValue));
        } else {
            $this->logMessage('Updating Collection Type: ' . $node->nodeValue);
        }

        $collectionType->setTypeName($this->decodeSpecialCharacters($node->getAttribute('name')));
        $collectionType->setDisplayGroupBy($this->decodeSpecialCharacters($node->getAttribute('displayGroupBy')));
        /**
         * TODO: Handle Deleted/Archived Relation Types, add in display group
         * and inverse name and inverse type handling
         */
        $collectionType->save();
    }

    private function importMasterDataVisibilityLevelObject(DOMElement $node) {
        $visibilityLevel = Doctrine::getTable('VisibilityLevel')->getOneByIdentifier($this->decodeSpecialCharacters($node->nodeValue));

        /**
         * Add this check to ensure we're not about to try and add a CollectionTYpe
         * becuase the identifier can't be found but the name already exists as
         * duplicate names will fail
         */
        if (Doctrine::getTable('VisibilityLevel')->checkIsNameAlreadyInUse($this->decodeSpecialCharacters($node->getAttribute('name')), $this->decodeSpecialCharacters($node->nodeValue))) {
            /**
             * The name is already in user override it.
             * if this fails then the import will require manual intervention to fix
             */
            $node->setAttribute('name', $node->getAttribute('name') . ' (' . $node->nodeValue . ')');
            $this->logMessage('Altering Imported Visiblity Level: ' . $node->nodeValue . ' = ' . $node->getAttribute('name'));
        }

        if ($visibilityLevel == null) {
            $this->logMessage('Adding Visibility Level: ' . $node->nodeValue);
            $visibilityLevel = new VisibilityLevel();
            $visibilityLevel->setTypeValue($this->decodeSpecialCharacters($node->nodeValue));
        } else {
            $this->logMessage('Updating Visibility Level: ' . $node->nodeValue);
        }

        $visibilityLevel->setTypeName($this->decodeSpecialCharacters($node->getAttribute('name')));
        $visibilityLevel->setDisplayGroupBy($this->decodeSpecialCharacters($node->getAttribute('displayGroupBy')));
        /**
         * TODO: Handle Deleted/Archived Relation Types, add in display group
         * and inverse name and inverse type handling
         */
        $visibilityLevel->save();
    }

    private function importMasterDataSubjectTagObject(DOMElement $node) {
        /**
         * Note: Subject Tags are not ControlledVocab Objects thus
         * they only have typename and displaygroupby and not typevalue fields
         */
        $subjectTag = Doctrine::getTable('SubjectTag')->findOneBy('typename', $this->decodeSpecialCharacters($node->nodeValue));
        if ($subjectTag == null) {
            $this->logMessage('Adding Subject Tag: ' . $node->nodeValue);
            $subjectTag = new SubjectTag();
            $subjectTag->setTypeName($this->decodeSpecialCharacters($node->nodeValue));
        } else {
            $this->logMessage('Updating Subject Tag: ' . $node->nodeValue);
        }


        $subjectTag->setDisplayGroupBy($this->decodeSpecialCharacters($node->getAttribute('displayGroupBy')));
        /**
         * TODO: Handle Deleted/Archived Relation Types, add in display group
         * and inverse name and inverse type handling
         */
        $subjectTag->save();
    }

    private function importMasterDataPropertyDetails(DOMElement $parent) {
        $nodeList = $parent->getElementsByTagName('propertyDetail');


        foreach ($nodeList as $node) {
            $this->importMasterDataPropertyDetailObject($node);

            $this->advanceAndCheckInsertCounter();
        }
    }

    private function importMasterDataRelationTypes(DOMElement $parent) {

        $nodeList = $parent->getElementsByTagName('relationType');
        foreach ($nodeList as $node) {
            $this->importMasterDataRelationTypeObject($node);

            $this->advanceAndCheckInsertCounter();
        }
    }

    private function importMasterDataCollectionTypes(DOMElement $parent) {

        $nodeList = $parent->getElementsByTagName('collectionType');
        foreach ($nodeList as $node) {
            $this->importMasterDataCollectionTypeObject($node);

            $this->advanceAndCheckInsertCounter();
        }
    }

    private function importMasterDataSubjectTags(DOMElement $parent) {

        $nodeList = $parent->getElementsByTagName('subjectTag');
        foreach ($nodeList as $node) {
            $this->importMasterDataSubjectTagObject($node);

            $this->advanceAndCheckInsertCounter();
        }
    }

    private function importMasterDataVisibilityLevels(DOMElement $parent) {

        $nodeList = $parent->getElementsByTagName('visibilityLevel');
        foreach ($nodeList as $node) {
            $this->importMasterDataVisibilityLevelObject($node);

            $this->advanceAndCheckInsertCounter();
        }
    }

    private function importMasterData(DOMElement $masterDataNode) {
        /**
         * Added due to bug in doctrine prepared statements leaving cursors
         * open after execution - this closes the connection which should
         * force it to re-open a new connection (and thus close all the cursors
         * and add new ones)
         */
        $this->resetDoctrineConnection();

        $nodeList = $masterDataNode->getElementsByTagName('propertyDetails');
        foreach ($nodeList as $node) {
            $this->importMasterDataPropertyDetails($node);
        }

        $this->resetDoctrineConnection();

        $nodeList = $masterDataNode->getElementsByTagName('relationTypes');
        foreach ($nodeList as $node) {
            $this->importMasterDataRelationTypes($node);
        }

        $this->resetDoctrineConnection();

        $nodeList = $masterDataNode->getElementsByTagName('collectionTypes');
        foreach ($nodeList as $node) {
            $this->importMasterDataCollectionTypes($node);
        }

        $this->resetDoctrineConnection();

        $nodeList = $masterDataNode->getElementsByTagName('subjectTags');
        foreach ($nodeList as $node) {
            $this->importMasterDataSubjectTags($node);
        }

        $this->resetDoctrineConnection();

        $nodeList = $masterDataNode->getElementsByTagName('visibilityLevels');
        foreach ($nodeList as $node) {
            $this->importMasterDataVisibilityLevels($node);
        }

        $this->resetDoctrineConnection();
    }

    private function processCollectionItemProperties(DOMElement $parent, CollectionItem $collectionItem) {
        $collectionProperties = $collectionItem->getCollectionProperties();
        /**
         * For now -> we'll just clear all the properties and re-create them 
         * later on we'll check and properly synchronize them
         */
        $this->logMessage('Deleting Properties for collection');
        /**
         * re-use primary keys where possible
         */
        $reuseKeys = $collectionProperties->getPrimaryKeys();
        /**
         * Sort the keys so we re-use them in order
         */
        sort($reuseKeys);

        $collectionProperties->clear();

        $propList = $parent->getElementsByTagName('property');
        foreach ($propList as $prop) {
            $collProp = new CollectionProperty();
            /**
             * Shift off the front item of the arrage and shorten the array
             */
            $collProp->setUin(array_shift($reuseKeys));
            $propertyDetail = Doctrine::getTable('PropertyDetail')->getOneByIdentifier($this->decodeSpecialCharacters($prop->getAttribute('identifier')));
            if ($propertyDetail != null) {
                $collProp->setPropertyDetail($propertyDetail);
                $collProp->setPropertyValue($this->decodeSpecialCharacters($prop->nodeValue));
                $collectionProperties->add($collProp);
                $this->logMessage('Added Property: ' . $prop->getAttribute('identifier') . ' to collection');
            } else {
                $this->logMessage('Unknown property identifier on collection item property: ' . $prop->getAttribute('identifier'));
            }
        }
    }

    private function processCollectionItemRelations(DOMElement $parent, CollectionItem $collectionItem) {
        $collectionRelations = $collectionItem->getCollectionRelations();
        $this->logMessage('Deleting Relations for collection');

        /**
         * re-use primary keys where possible
         */
        $reuseKeys = $collectionRelations->getPrimaryKeys();

        $collectionRelations->clear();
        $relationList = $parent->getElementsByTagName('relation');
        foreach ($relationList as $relation) {
            $relationObject = new CollectionRelation;
            $relationObject->setUin(array_pop($reuseKeys));
            $relationType = Doctrine::getTable('RelationType')->getOneByIdentifier($this->decodeSpecialCharacters($relation->getAttribute('relationType')));
            if ($relationType != null) {
                $relationObject->setRelationType($relationType);
                /**
                 * TODO put some error checking here - in case the related
                 * object does not exist.
                 */
                $relationObject->setRelatedItemUin($relation->nodeValue);
                $collectionRelations->add($relationObject);
                $this->logMessage('Added Relation: ' . $relation->getAttribute('relationType') . ' to collection');
            } else {
                $this->logMessage('Unknown relation type identifier on collection item relation: ' . $relation->getAttribute('relationType'));
            }
        }
    }

    private function processCollectionItemTags(DOMElement $parent, CollectionItem $collectionItem) {
        $collectionTags = $collectionItem->getCollectionTags();
        $this->logMessage('Deleting Tags for collection');
        /**
         * re-use primary keys where possible
         */
        $reuseKeys = $collectionTags->getPrimaryKeys();

        $collectionTags->clear();
        $tagList = $parent->getElementsByTagName('tag');
        foreach ($tagList as $tag) {
            $tagObject = new CollectionTag;
            $tagObject->setUin(array_pop($reuseKeys));
            $subjectTag = Doctrine::getTable('SubjectTag')->findOneBy('typename', $this->decodeSpecialCharacters($tag->nodeValue));
            if ($subjectTag != null) {
                $tagObject->setSubjectTag($subjectTag);
                $collectionTags->add($tagObject);
                $this->logMessage('Added subject tag: ' . $tag->nodeValue . ' to collection');
            } else {
                $this->logMessage('Unknown tag type identified on collection tag:' . $tag->nodeValue);
            }
        }
    }

    private function processCollectionItemResources(DOMElement $parent, CollectionItem $collectionItem) {
        /**
         * Note that get CollectionResources does not return the normal
         * Doctrine associated Doctrine_Collection  so deleting or adding
         * to this collection has no affect on the collection or resources
         */
        $collectionResources = $collectionItem->getCollectionResources();
        /**
         * re-use primary keys where possible
         */
        $reuseKeys = $collectionResources->getPrimaryKeys();

        $this->logMessage('Deleting resources for collection');
// $collectionResources->clear();
        foreach ($collectionResources as $res) {
            /**
             * Warning: force delete will remove all attached resrouces - should
             * not be done on root repository
             */
            $res->forceDelete();
        }

        $resourceList = $parent->getElementsByTagName('resource');
        foreach ($resourceList as $importResource) {
            $collResource = new CollectionResource();
            $collResource->setUin(array_pop($reuseKeys));

            $collResource->setResourceName($this->decodeSpecialCharacters($importResource->getElementsByTagName('resourceName')->item(0)->nodeValue));
            $collResource->setResourceLink($this->decodeSpecialCharacters($importResource->getElementsByTagName('resourceLink')->item(0)->nodeValue));
            $collResource->setOriginalFilename($this->decodeSpecialCharacters($importResource->getElementsByTagName('originalFilename')->item(0)->nodeValue));

            $importResourceData = $importResource->getElementsByTagName('resourceData')->item(0);

            if ($importResourceData->getAttribute('status') == 'exported') {
                /**
                 * IF the file is available in the export then copy it too the
                 * resource upload directory and let the ingest resources
                 * take care of the file later
                 */
                $uploadDir = $this->resourceIngestPath;
                if ($uploadDir == '') {
                    $uploadDir = Doctrine::getTable('CollectionResource')->getResourceUploadDir();
                }
                $importFilename = $this->resourceBasePath . DIRECTORY_SEPARATOR . $importResourceData->nodeValue;
                if ((strlen($importFilename) > 0) && (file_exists($importFilename))) {
                    /**
                     * Copy the file to the upload directory
                     */
                    copy($importFilename, $uploadDir . DIRECTORY_SEPARATOR . $collResource->getResourceLink());
                }
            } else {
                /**
                 * Mark the file as not available (pending)
                 */
            }
            $collResource->setRequiresIngest(true);
            $collResource->setResourceSize(0);
            $collResource->setBase64Encoded(false);
            $collResource->setCollectionUin($collectionItem->getUin());

// $collResource->setResourceData(null);
            /**
             * TODO add in processing resource files
             */
            $collResource->save();
            $this->logMessage('Added Resource: ' . $collResource->getResourceName());
        }
    }

    private function processCollectionItem(DOMElement $parent) {
        /**
         * Note Collections link on their UIN - thus the UIN must match
         * otherwise the collection will be considered a new collection
         */
        $collectionItem = Doctrine::getTable('CollectionItem')->find($parent->getAttribute('uin'));
        if ($collectionItem == null) {
            $this->logMessage('Adding Collection Item: ' . $parent->getAttribute('uin'));
            $collectionItem = new CollectionItem();
            $collectionItem->setUin($parent->getAttribute('uin'));
            /**
             * If the collection item doesn't exist then we can just go ahead
             *  and add in all the various details straight away
             */
        } else {
            $this->logMessage('Updating Collection Item: ' . $parent->getAttribute('uin'));
            /*
             * If the collection it does exist then we need to compare the
             * new list of properties to the old list of properties to see
             * which properties to add/remove/update
             * as so on with relations and resources
             */
        }


        $propertyGroups = $parent->getElementsByTagName('properties');
        foreach ($propertyGroups as $propertyGroup) {
            $this->processCollectionItemProperties($propertyGroup, $collectionItem);
        }

        $relationGroups = $parent->getElementsByTagName('relations');
        foreach ($relationGroups as $relationGroup) {
            $this->processCollectionItemRelations($relationGroup, $collectionItem);
        }

        $tagGroups = $parent->getElementsByTagName('tags');
        foreach ($tagGroups as $tagGroup) {
            $this->processCollectionItemTags($tagGroup, $collectionItem);
        }

        $resourceGroups = $parent->getElementsByTagName('resources');
        foreach ($resourceGroups as $resourceGroup) {
            $this->processCollectionItemResources($resourceGroup, $collectionItem);
        }
        /**
         * Always update these details
         */
        $collectionItem->setDeletedAt($parent->getAttribute('deletedAt') == '' ? null : $this->decodeSpecialCharacters($parent->getAttribute('deletedAt')));
        $collectionItem->setCreatedAt($parent->getAttribute('createdAt') == '' ? null : $this->decodeSpecialCharacters($parent->getAttribute('createdAt')));
        $collectionItem->setUpdatedAt($parent->getAttribute('updatedAt') == '' ? null : $this->decodeSpecialCharacters($parent->getAttribute('updatedAt')));
        $collectionItem->setApplicableStart($parent->getAttribute('applicableStart') == '' ? null : $this->decodeSpecialCharacters($parent->getAttribute('applicableStart')));
        $collectionItem->setApplicableEnd($parent->getAttribute('applicableEnd') == '' ? null : $this->decodeSpecialCharacters($parent->getAttribute('applicableEnd')));
        $collectionItem->setCollectionType(
                Doctrine::getTable('CollectionType')->getOneByIdentifier(
                        $this->decodeSpecialCharacters($parent->getAttribute('collectionType'))
                )
        );

        $visibilityLevel = Doctrine::getTable('VisibilityLevel')->getOneByIdentifier(
                        $this->decodeSpecialCharacters($parent->getAttribute('visibilityLevel')));

        if ($visibilityLevel != null) {
            $collectionItem->setVisibilityLevel($visibilityLevel);
        } else {
            $collectionItem->setVisibilityLevel(null);
        }


        /**
         * Set The collection name
         */
        $nameElements = $parent->getElementsByTagName('collectionName');
        $collectionItem->setCollectionName($this->decodeSpecialCharacters($nameElements->item(0)->nodeValue));


        $collectionItem->save();
        /**
         * Don't count reset the connection at every collectionITem
         */
        $this->resetDoctrineConnection();
    }

    private function checkExportVersion(DOMElement $exportNode) {
        $exportVersion = intval($exportNode->getAttribute('export-version'));
        if (($exportVersion >= $this->minExportVersion) && ($exportVersion <= $this->maxExportVersion)) {
            $this->logMessage('Export Version OK');
            return true;
        } else {
            $this->logMessage('Invalid Export Format Version. Export file is version: ' . $exportVersion . '; Minimum version: ' . $this->minExportVersion . ' Max version: ' . $this->maxExportVersion);
            return false;
        }
    }

    private function addHoldingCollections(DOMNodeList $collectionItemList) {
        $dummyCollectionType = Doctrine::getTable('CollectionType')->findAll()->getFirst();
        foreach ($collectionItemList as $collectionItemNode) {
            $collectionItem = Doctrine::getTable('CollectionItem')->find($collectionItemNode->getAttribute('uin'));
            /**
             * If the uin is not already delcared create a dummy collection to 
             * hold the data until the import fills it int 
             * this will stop related ITems from failing on any collections 
             * that relate to this uin.
             */
            if ($collectionItem == null) {
                $this->logMessage('Adding Placeholder Collection Item: ' . $collectionItemNode->getAttribute('uin'));
                $collectionItem = new CollectionItem();
                $collectionItem->setUin($collectionItemNode->getAttribute('uin'));
                $collectionItem->setCollectionName('Placeholder for: ' . $collectionItemNode->getAttribute('uin'));
                $collectionItem->setCollectionType($dummyCollectionType);
                $collectionItem->save();
                $this->advanceAndCheckInsertCounter();
            }
        }
    }

    private function processExportNode(DOMElement $exportNode) {
        $this->resetDoctrineConnection();

        if ($this->checkExportVersion($exportNode)) {
            $masterDataNodeList = $exportNode->getElementsByTagName('masterData');
            foreach ($masterDataNodeList as $masterDataNode) {
                $this->importMasterData($masterDataNode);
            }

            $collectionItemList = $exportNode->getElementsByTagName('collectionItem');
            $this->addHoldingCollections($collectionItemList);
            foreach ($collectionItemList as $collectionItemNode) {
                $this->processCollectionItem($collectionItemNode);
            }
        }
    }

    public function importData($filename) {
        $domDocument = new DOMDocument();
        $domDocument->load($filename);
        $exportNodeList = $domDocument->getElementsByTagName('travideExport');
        foreach ($exportNodeList as $exportNode) {
            $this->processExportNode($exportNode);
        }
    }

}

?>
